package org.esgi.dns.record;

import java.io.*;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.util.*;

/**
 * This class is used to load or save in a term.
 *
 * @author Mohamed-Amine Chouchene
 */
public class FileRecord {

    public static String commentPrefix = "#";
    private boolean _status = false;
    private File _file;
    private Map<String, Object> _table = new HashMap<String, Object>();

    /**
     * Constructor that take a String path as parameter.
     *
     * @param filePath String - path of the file to load.
     * @param create boolean - tell wether or not the file has to be created.
     * @throws Exception
     * @see FileTerm#FileTerm(java.io.File, boolean)
     */
    public FileRecord(String filePath, boolean create) throws Exception {
        this(new File(filePath), create);
    }

    /**
     * FileTerm constructor. Load given file if exist.
     *
     * @param f File - the file to load.
     * @param create boolean - tell whether or not the file has to be created.
     * @throws Exception
     */
    public FileRecord(File f, boolean create) throws Exception {
        this._status = create;
        this._file = f;
        if (!this._status) {
            if (!_file.exists()) {
                return;//throw new IOException("this file does not exist.");
            }
            if (!_file.isFile()) {
                return;//throw new IOException("this file is not a file.");
            }
        } else {
        	//new FileOutputStream(_file).close();
        	 if (!_file.exists())
        		 f.createNewFile();
        }
        loadFile();
//        loadFileMMIO(); // Load file with Memory Mapped IO, could be quicker on some OS
    }

    /**
     * Constructor with default value for parameter create (false).
     *
     * @param f File - the file to load.
     * @throws Exception
     * @see FileTerm#FileTerm(java.io.File, boolean)
     */
    public FileRecord(File f) throws Exception {
        this(f, false);
    }

    /**
     * Constructor with default value for parameter create (false).
     *
     * @param filePath String - path of the file to load.
     * @throws Exception
     * @see FileTerm#FileTerm(java.io.File, boolean)
     */
    public FileRecord(String filePath) throws Exception {
        this(filePath, false);
    }
    
    /**
     * Fill the attribute table ordered per type.
     *
     * @see FileTerm#_table
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public final void loadFileMMIO() throws Exception {
        if (!_file.canRead()) {
            throw new Exception("this file cannot be read.");
        }
        
        FileInputStream fis = null;
        
        try {
			fis = new FileInputStream(_file);
			FileChannel fc = fis.getChannel();
			MappedByteBuffer mbb = fc.map(MapMode.READ_ONLY, 0, fc.size());
			byte[] buffer = new byte[(int)fc.size()];
			mbb.get(buffer);
			fis.close();
			
			BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buffer)));
			String currentSection = null;
			
			for (String line = in.readLine(); line != null; line = in.readLine()) {
			    
				if (!line.startsWith(commentPrefix)) {
			        if (line.startsWith("[")) {
			            if (!line.endsWith("]")) {
			                throw new Exception("Invalid format: data found outside section");
			            } else {
			                currentSection = line.trim().substring(1, line.length() - 1);
			            }
			        } else {
			            line = line.trim();
			            int pos = -1;
			            if ((pos = line.indexOf('=')) != -1 && currentSection != null) {
			                String key = line.substring(0, pos);
			                String value = line.substring(pos + 1, line.length()).trim();
			                if (value.startsWith("{{")) {
			                    value = value.replace("{{", "");
			                    if (!value.endsWith("}}")) {
			                        throw new Exception("Invalid format data");
			                    }
			                    value = value.replace("}}", "").trim();
			                    String[] list = value.split(",");
			                    if (null != list && 0 < list.length) {
			                        List<String> att = new ArrayList<String>();
			                        att.addAll(Arrays.asList(list));
			                        if (null != _table.get(currentSection)) {
			                            Map<String, List<String>> rels = (Map<String, List<String>>) _table.get(currentSection);
			                            rels.put(key, att);
			                        } else {
			                            Map<String, List<String>> map = new HashMap<String, List<String>>();
			                            map.put(key, att);
			                            _table.put(currentSection, map);
			                        }
			                    }
			                } else {
			                    if (null != _table.get(currentSection)) {
			                        Map<String, String> rels = (Map<String, String>) _table.get(currentSection);
			                        rels.put(key, value);
			                    } else {
			                        Map<String, String> map = new HashMap<String, String>();
			                        map.put(key, value);
			                        _table.put(currentSection, map);
			                    }

			                }
			            } else if (currentSection != null) {
			                if (null != _table.get(currentSection)) {
			                    List<String> rels = (List<String>) _table.get(currentSection);
			                    rels.add(line);
			                } else {
			                    List<String> rels = new ArrayList<String>();
			                    rels.add(line);
			                    _table.put(currentSection, rels);
			                }
			            }
			        }
			    }
			  }
		} catch (Exception e) {
			e.printStackTrace();
		}
        finally {
        	if(fis != null)
            {
                try
                {
                	fis.close();
                }
                catch(final Exception ex)
                {
                    ex.printStackTrace();
                }
            }
        }

    }

    /**
     * Fill the attribute table ordered per type.
     *
     * @see FileTerm#_table
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public final void loadFile() throws Exception {
        if (!_file.canRead()) {
            throw new Exception("this file cannot be read.");
        }
        FileInputStream fis = new FileInputStream(_file);
        InputStreamReader isr = new InputStreamReader(fis, "UTF8");
        	BufferedReader readBuffer = new BufferedReader(isr);
            String line = null;
            String currentSection = null;
            try {
                while ((line = readBuffer.readLine()) != null) {
                    if (!line.startsWith(commentPrefix)) {
                        if (line.startsWith("[")) {
                            if (!line.endsWith("]")) {
                                throw new Exception("Invalid format: data found outside section");
                            } else {
                                currentSection = line.trim().substring(1, line.length() - 1);
                            }
                        } else {
                            line = line.trim();
                            int pos = -1;
                            if ((pos = line.indexOf('=')) != -1 && currentSection != null) {
                                String key = line.substring(0, pos);
                                String value = line.substring(pos + 1, line.length()).trim();
                                if (value.startsWith("{{")) {
                                    value = value.replace("{{", "");
                                    if (!value.endsWith("}}")) {
                                        throw new Exception("Invalid format data");
                                    }
                                    value = value.replace("}}", "").trim();
                                    String[] list = value.split(",");
                                    if (null != list && 0 < list.length) {
                                        List<String> att = new ArrayList<String>();
                                        att.addAll(Arrays.asList(list));
                                        if (null != _table.get(currentSection)) {
                                            Map<String, List<String>> rels = (Map<String, List<String>>) _table.get(currentSection);
                                            rels.put(key, att);
                                        } else {
                                            Map<String, List<String>> map = new HashMap<String, List<String>>();
                                            map.put(key, att);
                                            _table.put(currentSection, map);
                                        }
                                    }
                                } else {
                                    if (null != _table.get(currentSection)) {
                                        Map<String, String> rels = (Map<String, String>) _table.get(currentSection);
                                        rels.put(key, value);
                                    } else {
                                        Map<String, String> map = new HashMap<String, String>();
                                        map.put(key, value);
                                        _table.put(currentSection, map);
                                    }

                                }
                            } else if (currentSection != null) {
                                if (null != _table.get(currentSection)) {
                                    List<String> rels = (List<String>) _table.get(currentSection);
                                    rels.add(line);
                                } else {
                                    List<String> rels = new ArrayList<String>();
                                    rels.add(line);
                                    _table.put(currentSection, rels);
                                }
                            }
                        }
                    }

                }
                fis.close();
                isr.close();
                readBuffer.close();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            finally{
            	if(null!=readBuffer)
            		readBuffer.close();
            }
    }

    /**
     * Write the attributes stored in FileTerm#_table in current FileTerm#_file.
     *
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    public void saveFile() throws Exception {
        if (!_file.canWrite()) {
            throw new Exception("this file cannot be written.");
        }
        BufferedWriter writeBuffer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(_file)));
        try {
            for (String key : _table.keySet()) {
                writeBuffer.write("[" + key + "]");
                writeBuffer.newLine();
                Object o = _table.get(key);
                if (o instanceof List) {
                    List<String> list = (List<String>) o;
                    for (String string : list) {
                        writeBuffer.write(string);
                        writeBuffer.newLine();
                    }
                } else if (o instanceof Map) {
                    Map<String, Object> ob = (Map<String, Object>) o;
                    for (String str : ob.keySet()) {
                        if (ob.get(str) instanceof List) {
                            List<String> list = (List<String>) ob.get(str);
                            if (null != list && 0 < list.size()) {
                                writeBuffer.write(str + "=" + list.toString().replace("[", "{{").replace("]", "}}"));
                                writeBuffer.newLine();
                            }
                        } else if (ob.get(str) instanceof String) {
                            writeBuffer.write(str + "=" + ob.get(str));
                            writeBuffer.newLine();
                        }
                    }
                }

            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }finally{
        	if(null!=writeBuffer)
        		writeBuffer.close();
        }
    }

    /**
     * Tell wheter or not a key is present in Fileterm#_table.
     *
     * @param section String - name of the key to look for
     * @return boolean - True if the key exist in Fileterm#_table, false
     * otherwise.
     */
    public boolean sectionExists(String section) {
        return _table.containsKey(section);
    }

    /**
     * Return the value associated with given key in Fileterm#_table.
     *
     * @param section String - name of the key to look for
     * @return Object - The value associated with given value or null or section
     * is null.
     */
    public Object getSection(String section) {
        if (null != section) {
            return _table.get(section);
        } else {
            return null;
        }
    }

    /**
     * Store a value in Fileterm#_table if the key does not already exist.
     *
     * @param section String - key
     * @param value String - value
     */
    public void putProperty(String section, Object value) {
        if (!sectionExists(section) && null != value) {
            _table.put(section, value);
        }
    }

    /**
     * Delete the pair key/value from Fileterm#_table.
     *
     * @param section String - section
     */
    public void removeSection(String section) {
        if (sectionExists(section)) {
            _table.remove(section);
        }
    }

    @Override
    /**
     * @return String - the content of FileTerm#_table.
     */
    public String toString() {
        return _table.toString();
    }
}
